package com.zzjson.mvcframework.servlet;

import com.zzjson.mvcframework.annoation.*;
import lombok.Getter;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * <p>****************************************************************************</p>
 * <p><b>Copyright © 2010-2019 shuncom team All Rights Reserved<b></p>
 * <ul style="margin:15px;">
 * <li>Description : com.zzjson.mvcframework.servlet</li>
 * <li>Version     : 1.0.0</li>
 * <li>Creation    : 2019年05月10日</li>
 * <li>@author     : zzy0_0</li>
 * </ul>
 * <p>****************************************************************************</p>
 */
public class MyDispatcherServlet extends HttpServlet {
	private Properties contextConfig = new Properties();

	List<String> classNames = new ArrayList<>();

	private Map<String, Object> ioc = new HashMap<>();

	//保存url和Method的对应关系
	// private Map<String, Method> handlerMapping = new HashMap<String, Method>();
	private List<Handler> handlerMapping = new ArrayList<>();

	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		doPost(req, resp);
	}

	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		System.out.println("enter post");
		//6、调用，运行阶段
		try {
			doDispatch(req, resp);
		} catch (Exception e) {
			e.printStackTrace();
			resp.getWriter().write("500 Exection,Detail : " + Arrays.toString(e.getStackTrace()));
		}

	}

	private void doDispatch(HttpServletRequest req, HttpServletResponse resp) {
		try {

			Handler handler = getHandler(req);


			if (handler == null) {
				resp.getWriter().write("404 Not Found!!!");
				return;
			}


			Class<?>[] paramTypes = handler.getParamTypes();
			Object[] paramValues = new Object[paramTypes.length];

			//参数列表
			Map<String, String[]> parameterMap = req.getParameterMap();

			/**
			 * 设置参数列表
			 */
			for (Map.Entry<String, String[]> param : parameterMap.entrySet()) {
				String value = Arrays.toString(param.getValue()).replaceAll("\\[|]", "");
				if (!handler.paramIndexMapping.containsKey(value)) {
					continue;
				}
				int index = handler.paramIndexMapping.get(param.getKey());
				//转化值
				paramValues[index] = convert(paramTypes[index], value);
			}
			/**
			 * 给request和response赋值
			 */
			if (handler.paramIndexMapping.containsKey(HttpServletRequest.class.getName())) {
				Integer integer = handler.paramIndexMapping.get(HttpServletRequest.class.getName());
				paramValues[integer] = req;
			}

			if (handler.paramIndexMapping.containsKey(HttpServletResponse.class.getName())) {
				Integer integer = handler.paramIndexMapping.get(HttpServletResponse.class.getName());
				paramValues[integer] = resp;
			}


			//执行方法
			Object returnValue = handler.method.invoke(handler.controller, paramValues);

			if (returnValue == null || returnValue instanceof Void) {
				return;
			}
			//todo 视图渲染
			resp.getWriter().write(returnValue.toString());
		} catch (IOException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		}


	}

	/**
	 * 转化
	 *
	 * @param type
	 * @param value
	 * @return
	 */
	private Object convert(Class<?> type, String value) {
		//如果是int
		if (Integer.class == type) {
			return Integer.valueOf(value);
		} else if (Double.class == type) {
			return Double.valueOf(value);
		}
		return value;
	}

	private Handler getHandler(HttpServletRequest req) {
		if (handlerMapping.isEmpty()) {
			return null;
		}
		//绝对路径
		String url = req.getRequestURI();
		//处理成相对路径
		String contextPath = req.getContextPath();
		url = url.replaceAll(contextPath, "").replaceAll("/+", "/");

		for (Handler handler : this.handlerMapping) {
			Matcher matcher = handler.getPattern().matcher(url);
			if (!matcher.matches()) {
				continue;
			}
			return handler;
		}
		return null;
	}

	@Override
	public void init(ServletConfig config) throws ServletException {
		super.init(config);
		//加载配置文件
		doLoadConfig(config.getInitParameter("contextConfiguration"));

		//	扫描相关类
		doScanner(contextConfig.getProperty("scanPackage"));

		//	装载对应注解类,并且放到ico容器中
		doInstance();
		//	依赖注入
		doAutowired();
		//	初始化handlermapping
		initHandlerMapping();
		System.out.println("my framework is init;");

	}

	private void initHandlerMapping() {
		if (ioc.isEmpty()) {
			return;
		}

		for (Map.Entry<String, Object> entry : ioc.entrySet()) {
			Class<?> clazz = entry.getValue().getClass();

			if (!clazz.isAnnotationPresent(MyController.class)) {
				continue;
			}


			//保存写在类上面的@GPRequestMapping("/demo")
			String baseUrl = "";
			if (clazz.isAnnotationPresent(MyRequestMapping.class)) {
				MyRequestMapping requestMapping = clazz.getAnnotation(MyRequestMapping.class);
				baseUrl = requestMapping.value();
			}

			//默认获取所有的public方法
			for (Method method : clazz.getMethods()) {
				if (!method.isAnnotationPresent(MyRequestMapping.class)) {
					continue;
				}

				MyRequestMapping requestMapping = method.getAnnotation(MyRequestMapping.class);
				//优化
				// //demo///query
				String regex = ("/" + baseUrl + "/" + requestMapping.value())
						.replaceAll("/+", "/");
				Pattern pattern = Pattern.compile(regex);
				handlerMapping.add(new Handler(pattern, entry.getValue(), method));
				System.out.println("Mapped :" + regex + "," + method);

			}


		}
	}

	private void doAutowired() {
		if (ioc.isEmpty()) {
			return;
		}
		for (Map.Entry<String, Object> entry : ioc.entrySet()) {
			Field[] declaredFields = entry.getValue().getClass().getDeclaredFields();
			for (Field declaredField : declaredFields) {
				if (!declaredField.isAnnotationPresent(MyAutowired.class)) {
					continue;
				}
				MyAutowired autowired = declaredField.getAnnotation(MyAutowired.class);
				String beanName = autowired.value().trim();

				//只实现了注入接口
				if ("".equals(beanName)) {
					beanName = declaredField.getType().getName();
				}
				declaredField.setAccessible(true);


				try {
					//动态赋值
					declaredField.set(entry.getValue(), ioc.get(beanName));
				} catch (IllegalAccessException e) {
					e.printStackTrace();
				}
			}
		}
	}

	private void doInstance() {
		if (classNames.isEmpty()) {
			return;
		}
		try {
			for (String className : classNames) {
				Class<?> aClass = Class.forName(className);

				if (aClass.isAnnotationPresent(MyController.class)) {
					Object instance = aClass.newInstance();
					String simpleName = aClass.getSimpleName();
					String beanName = toLowerFirstCase(simpleName);
					ioc.put(beanName, instance);
				} else if (aClass.isAnnotationPresent(MyService.class)) {

					String beanName = aClass.getAnnotation(MyService.class).value();
					if ("".equals(beanName.trim())) {
						beanName = toLowerFirstCase(aClass.getSimpleName());
					}
					Object instance = aClass.newInstance();
					ioc.put(beanName, instance);

					//根据类型自动复制
					for (Class<?> i : aClass.getInterfaces()) {
						if (ioc.containsKey(i.getName())) {
							throw new Exception("The “" + i.getName() + "” is exists!!");
						}
						//接口的类型直接当成key
						ioc.put(i.getName(), instance);
					}

				} else {
					continue;

				}

			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private String toLowerFirstCase(String simpleName) {
		char[] chars = simpleName.toCharArray();
		chars[0] += 32;
		return String.valueOf(chars);

	}

	private void doScanner(String scanPackage) {
		String scanLocation = scanPackage.replaceAll("\\.", "/");
		URL resource = this.getClass().getClassLoader().getResource("/" + scanLocation);
		File file = new File(resource.getFile());
		for (File listFile : file.listFiles()) {
			if (listFile.isDirectory()) {
				doScanner(scanPackage + "." + listFile.getName());
			} else {
				if (!listFile.getName().endsWith(".class")) {
					continue;
				}
				String className = (scanPackage + "." + listFile.getName().replace(".class", ""));
				classNames.add(className);
			}
		}

	}

	private void doLoadConfig(String contextConfigLocation) {
		InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
		try {
			contextConfig.load(resourceAsStream);
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if (resourceAsStream != null) {
				try {
					resourceAsStream.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}

		}

	}

	@Getter
	private class Handler {
		/**
		 * 保存方法对应的实例
		 */
		protected Object controller;

		/**
		 * 映射的方法
		 */
		protected Method method;

		protected Pattern pattern;

		private Class<?>[] paramTypes;

		/**
		 * 参数顺序
		 */
		protected Map<String, Integer> paramIndexMapping;

		protected Handler(Pattern pattern, Object controller, Method method) {
			this.pattern = pattern;
			this.controller = controller;
			this.method = method;
			this.paramTypes = method.getParameterTypes();
			paramIndexMapping = new HashMap<String, Integer>();
			putParamIndexMapping(method);
		}

		private void putParamIndexMapping(Method method) {
			Annotation[][] parameterAnnotations = method.getParameterAnnotations();
			for (int i = 0; i < parameterAnnotations.length; i++) {
				for (Annotation a : parameterAnnotations[i]) {
					if (a instanceof MyRequestParam) {
						String requestName = ((MyRequestParam) a).value();
						if ("" != requestName) {
							paramIndexMapping.put(requestName, i);
						}
					}
				}
			}
			//	request 和 response需要单独提取
			Class<?>[] parameterTypes = method.getParameterTypes();
			for (int i = 0; i < parameterTypes.length; i++) {
				if (parameterTypes[i] == HttpServletRequest.class ||
						parameterTypes[i] == HttpServletResponse.class
				) {
					paramIndexMapping.put(parameterTypes[i].getName(), i);
				}
			}
		}

	}
}